package net.ermannofranco.genericdaojdbc.dao;

import static net.ermannofranco.genericdaojdbc.model.Entity.ID_PROP_NAME;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import net.ermannofranco.genericdaojdbc.clause.Clause;
import net.ermannofranco.genericdaojdbc.clause.ClauseBuilder;
import net.ermannofranco.genericdaojdbc.clause.ComparationClause;
import net.ermannofranco.genericdaojdbc.clause.HavingClause;
import net.ermannofranco.genericdaojdbc.model.Entity;
import net.ermannofranco.genericdaojdbc.model.annotation.ColumnMapping;
import net.ermannofranco.genericdaojdbc.model.annotation.ColumnMappings;
import net.ermannofranco.genericdaojdbc.model.annotation.ExcludeFromInsert;
import net.ermannofranco.genericdaojdbc.model.annotation.ForeignEntities;
import net.ermannofranco.genericdaojdbc.model.annotation.IdColumn;
import net.ermannofranco.genericdaojdbc.model.annotation.ManyToOne;
import net.ermannofranco.genericdaojdbc.model.annotation.Tablename;
import net.ermannofranco.genericdaojdbc.util.Comparation;
import net.ermannofranco.genericdaojdbc.util.Fn;
import net.ermannofranco.genericdaojdbc.util.GenericUtils;
import net.ermannofranco.genericdaojdbc.util.Obj;
import net.ermannofranco.genericdaojdbc.util.Order;
import net.ermannofranco.genericdaojdbc.util.StatFunc;
import net.ermannofranco.genericdaojdbc.util.Util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

/**
 * Questa classe puo' venire anche utilizzata direttamente, basta invocare il
 * costruttore ed estenderla trivialmente con {}. E' astratta per mantenere le
 * info sui generics a runtime.
 * 
 * @author ermanno (ermanno.franco@gmail.com)
 * 
 * @param <T>
 *            Entity Pojo.
 * @param <K>
 *            Numeric primary key, extends Number.
 */

public abstract class AbstractGenericDao<T extends Entity<K>, K extends Number>
		implements GenericDao<T, K> {

	private static final Object CLASS_PROP_NAME = "class";

	protected final transient Log log = LogFactory.getLog(getClass());

	protected SimpleJdbcTemplate template;

	protected SimpleJdbcInsert insert;

	private final List<String> excludedFields = new ArrayList<String>();

	private final Map<String, String> columnMappings = new HashMap<String, String>();

	/**
	 * Class of the Entity which this dao refers to.
	 */
	private final Class<T> entityClazz;

	private final Map<String, String> insertMapping = new HashMap<String, String>();

	private boolean isGeneratedId;

	private String idColumn = Entity.ID_PROP_NAME;

	private ManyToOne[] manyToOnes = new ManyToOne[0];

	protected ApplicationContext context;

	protected String tablename;

	/**
	 * Entity class Property descriptors.
	 */
	private final PropertyDescriptor[] pd;

	@SuppressWarnings("unchecked")
	public AbstractGenericDao() {
		entityClazz = (Class<T>) GenericUtils.getTypeArguments(
				AbstractGenericDao.class, getClass()).get(0);
		log.debug("entityClazz: " + entityClazz);
		pd = BeanUtils.getPropertyDescriptors(entityClazz);

		// introspect to get a list of annotations on class.
		Annotation[] annotations = entityClazz.getDeclaredAnnotations();

		// iterate over the annotations to locate the
		// ExcludeFromInsert,ColumnMapping,ColumnMappings,IdColumn
		for (Annotation a : annotations) {
			if (a instanceof ExcludeFromInsert) {
				ExcludeFromInsert ml = (ExcludeFromInsert) a; // here it is !!!
				if (ml.value() != null && ml.value().length > 0)
					excludedFields.addAll(Arrays.asList(ml.value()));
			} else if (a instanceof ForeignEntities) {
				ForeignEntities fk = (ForeignEntities) a;
				manyToOnes = fk.manyToOne();
				// gestire le eventuali annotazioni mancanti, property,daoBeanId
				// e column devono sempre esserci
				for (int i = 0; i < manyToOnes.length; i++) {
					excludedFields.add(manyToOnes[i].property());
				}

				// TODO per altri tipi di many

			} else if (a instanceof IdColumn) {
				IdColumn ic = (IdColumn) a;
				isGeneratedId = ic.generated();
				String id = ic.column();
				idColumn = ((id == null || id.length() == 0) ? Util
						.toUP(Entity.ID_PROP_NAME) : id);

				columnMappings.put(Entity.ID_PROP_NAME, idColumn);

				if (isGeneratedId
						&& !excludedFields.contains(Entity.ID_PROP_NAME))
					excludedFields.add(Entity.ID_PROP_NAME);

			} else if (a instanceof ColumnMappings) {
				ColumnMappings cm = (ColumnMappings) a;
				ColumnMapping[] value = cm.value();
				for (int i = 0; i < value.length; i++) {
					columnMappings.put(value[i].property(), value[i].column());
				}
			} else if (a instanceof ColumnMapping) {
				ColumnMapping cm = (ColumnMapping) a;
				columnMappings.put(cm.property(), cm.column());
			} else if (a instanceof Tablename) {
				Tablename t = (Tablename) a;
				tablename = t.value();
			}
		}
		// default tabella come nome entity in MAIUSCOLO.
		if (tablename == null || tablename.length() == 0) {
			tablename = getEntityClass().getSimpleName().toUpperCase();
		}

		calculateInsertColumns();
		fillColumnMappings();

	}

	/**
	 * Istanzia il SimpleJdbcTemplate direttamente. I nostri dao costruiscono il
	 * JdbcTemplate direttamente a partire dal dataSource. Vedi
	 * spring-reference, $11.2.1.2
	 * 
	 * @param ds
	 */
	@Required
	public final void setDataSource(DataSource ds) {
		template = new SimpleJdbcTemplate(ds);
		insert = new SimpleJdbcInsert(ds).withTableName(tablename)
				.usingColumns(insertMapping.values().toArray(new String[0]));
		if (isGeneratedId)
			insert.usingGeneratedKeyColumns(idColumn);
		insert.compile();
	}

	/**
	 * Fissa il mapping di tutte le props.
	 */
	private void fillColumnMappings() {

		for (int i = 0; i < pd.length; i++) {
			String name = pd[i].getName();
			log.debug("name: " + name + "; class: "
					+ pd[i].getPropertyType().getSimpleName());
			// skip entity properties
			if (Entity.class.isAssignableFrom(pd[i].getPropertyType()))
				continue;
			if (CLASS_PROP_NAME.equals(name))
				continue;
			String column = columnMappings.get(name);
			// la colonna e' il nome prop in MAIUSCOLO
			if (column == null)
				columnMappings.put(name, Util.toUP(name));
			log.debug(name + " mapped to " + columnMappings.get(name));
		}
	}

	/**
	 * fissa i nomi del mapping per l'insert.
	 */
	private void calculateInsertColumns() {
		String name;
		outer: for (int i = 0; i < pd.length; i++) {

			name = pd[i].getName();
			if (CLASS_PROP_NAME.equals(name))
				continue;
			for (int j = 0; j < manyToOnes.length; j++) {
				if (manyToOnes[j].property().equals(name)) {
					insertMapping.put(name, manyToOnes[j].column());
					continue outer;
				}
			}
			for (int j = 0; j < excludedFields.size(); j++) {
				if (excludedFields.get(j).equals(name))
					continue outer;
			}
			for (String prop : columnMappings.keySet()) {
				if (prop.equals(name)) {
					insertMapping.put(name, columnMappings.get(prop));
					continue outer;
				}
			}
			insertMapping.put(name, Util.toUP(name));
		}
		log.debug("insertMapping: " + insertMapping);
	}

	/**
	 * Lettura globale
	 */
	@Transactional(readOnly = true)
	public List<T> listAll() {
		String sql = SELECT_ALL_FROM + tablename + SEMICOLON;
		List<T> ret = template.query(sql, getRowMapper());
		return ret;
	}

	/**
	 * Lettura globale ordinata, si devono passare i nomi delle proprieta', lato
	 * java.
	 */
	@Transactional(readOnly = true)
	public List<T> listAllOrdered(Order... orderFields) {
		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename);
		if (orderFields != null && orderFields.length > 0) {
			sb.append(ORDER_BY);
			for (Order o : orderFields) {
				String colName = columnMappings.get(o.getField());
				// if (colName == null)
				// colName = Util.toUP(o.getField());
				sb.append(colName).append(o.isAsc() ? ASC : DESC).append(
						COMMA_W_SPACE);
			}
			sb.setLength(sb.length() - 2);
		}
		sb.append(SEMICOLON);
		List<T> ret = template.query(sb.toString(), getRowMapper());
		return ret;
	}

	@Transactional(readOnly = true)
	public List<T> listByPropertyValue(String propName, Object value) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);

		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		sb.append(colName).append(EQUALS_COLON).append(propName).append(
				SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		List<T> ret = template.query(sb.toString(), getRowMapper(), source);
		return ret;
	}

	@Transactional(readOnly = true)
	public List<T> listByPropertyValue(String propName, Comparation c,
			Object value) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);

		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		sb.append(colName).append(c.val()).append(COLON).append(propName)
				.append(SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		List<T> ret = template.query(sb.toString(), getRowMapper(), source);
		return ret;
	}

	@Transactional(readOnly = true)
	public List<T> listByPropertyValue(String propName, Object value,
			Order... orderFields) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		sb.append(colName).append(EQUALS_COLON).append(propName);
		if (orderFields != null && orderFields.length > 0) {
			sb.append(ORDER_BY);
			for (Order o : orderFields) {
				String colName2 = columnMappings.get(o.getField());
				if (colName2 == null)
					colName2 = Util.toUP(o.getField());
				sb.append(colName2).append(o.isAsc() ? ASC : DESC).append(
						COMMA_W_SPACE);
			}
			sb.setLength(sb.length() - 2);
		}
		sb.append(SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		List<T> ret = template.query(sb.toString(), getRowMapper(), source);
		return ret;
	}

	@Transactional(readOnly = true)
	public List<T> listByPropertyValue(String propName, Comparation c,
			Object value, Order... orderFields) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);

		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		sb.append(colName).append(c.val()).append(COLON).append(propName);
		if (orderFields != null && orderFields.length > 0) {
			sb.append(ORDER_BY);
			for (Order o : orderFields) {
				String colName2 = columnMappings.get(o.getField());
				if (colName2 == null)
					colName2 = Util.toUP(o.getField());

				sb.append(colName2).append(o.isAsc() ? ASC : DESC).append(
						COMMA_W_SPACE);
			}
			sb.setLength(sb.length() - 2);
		}
		sb.append(SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		List<T> ret = template.query(sb.toString(), getRowMapper(), source);
		return ret;
	}

	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public T getById(K id) {
		List<T> list = listByIds(id);
		return list.size() == 0 ? null : list.get(0);
	}

	@Transactional(readOnly = true)
	public List<T> listByIds(K... ids) {
		if (ids == null || ids.length == 0) {
			log.warn("ids not valid");
			return new ArrayList<T>();
		}
		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE).append(idColumn).append(IN).append(
				BRACKET_OPEN).append(ID_WITH_COLON).append(BRACKET_CLOSED);
		sb.append(SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(ID, Arrays
				.asList(ids));
		return template.query(sb.toString(), getRowMapper(), source);
	}

	// @Transactional(readOnly = true)
	// public List<T> listByIdsOrderBy(Order orderField, K... ids) {
	// if (ids == null) {
	// log.warn("ids null!!!");
	// return new ArrayList<T>();
	// }
	//
	// StringBuilder sb = new StringBuilder(SELECT_FROM);
	// sb.append(tablename).append(WHERE).append(idColumn).append(IN).append(
	// BRACKET_OPEN).append(ID_WITH_COLON).append(BRACKET_CLOSED);
	// if (orderField != null) {
	// sb.append(ORDER_BY).append(orderField.getField());
	// }
	// sb.append(SEMICOLON);
	// MapSqlParameterSource source = new MapSqlParameterSource(ID, Arrays
	// .asList(ids));
	// return template.query(sb.toString(), getRowMapper(), source);
	// }

	// /**
	// * Rimozione singola
	// */
	// @SuppressWarnings("unchecked")
	// public boolean delete(K id) {
	// return remove(id);
	// }

	/**
	 * Rimozione multipla
	 */
	@Transactional
	public boolean remove(K... ids) {
		if (ids == null || ids.length == 0) {
			log.warn("ids not valid!!!");
			return false;
		}

		StringBuilder sb = new StringBuilder(DELETE_FROM);
		sb.append(tablename).append(WHERE).append(idColumn).append(IN).append(
				BRACKET_OPEN).append(ID_WITH_COLON).append(BRACKET_CLOSED)
				.append(SEMICOLON);
		List<K> list = Arrays.asList(ids);

		MapSqlParameterSource source = new MapSqlParameterSource(ID, list);
		return template.update(sb.toString(), source) > 0;
	}

	/**
	 * Rimozione totale
	 */
	@Transactional
	public boolean removeAll() {
		String sql = DELETE_FROM + tablename + SEMICOLON;
		return template.update(sql, EMPTY_ARR) > 0;
	}

	/**
	 * inserimento. Restituisce l'id solo nel caso che sia autogenerato,
	 * altrimenti restituisce l'update count
	 */
	@Transactional
	public Number insert(T t) {
		log.debug("about inserting " + t);
		if (t == null) {
			log.error("oggetto null, impossibile salvare");
			return 0;
		}
		if (isGeneratedId)
			return insert.executeAndReturnKey(getSqlParameterSource(t));
		else
			return insert.execute(getSqlParameterSource(t));
	}

	@SuppressWarnings("unchecked")
	private SqlParameterSource getSqlParameterSource(T t) {
		log.debug("entity: " + t);
		MapSqlParameterSource source = new MapSqlParameterSource();
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(t);
		for (String propName : insertMapping.keySet()) {
			Object value = bw.getPropertyValue(propName);
			log.debug("propName:" + propName + "; value: " + value);
			source.addValue(propName, value);
		}
		for (int i = 0; i < manyToOnes.length; i++) {
			Entity e = (Entity) bw.getPropertyValue(manyToOnes[i].property());
			source.addValue(manyToOnes[i].column(), e.getId());
		}
		log.debug("source: " + source.getValues());
		return source;
	}

	@SuppressWarnings("unchecked")
	private SqlParameterSource[] getBatchSqlParameterSource(List<T> list) {
		Map<String, Object>[] values = new HashMap[list.size()];
		for (int k = 0; k < list.size(); k++) {
			Map<String, Object> source = new HashMap<String, Object>();
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(list
					.get(k));
			for (String propName : insertMapping.keySet()) {
				Object value = bw.getPropertyValue(propName);
				source.put(propName, value);
			}
			for (int i = 0; i < manyToOnes.length; i++) {
				Entity e = (Entity) bw.getPropertyValue(manyToOnes[i]
						.property());
				source.put(manyToOnes[i].property(), e.getId());
			}
			values[k] = source;
		}
		SqlParameterSource[] batch = SqlParameterSourceUtils
				.createBatch(values);
		// log.debug("source: " + source);
		return batch;
	}

	protected final Class<T> getEntityClass() {
		return entityClazz;
	}

	/**
	 * Implementazione di default, puo' essere sovrascritta.
	 * 
	 * @return
	 */
	protected RowMapper<T> getRowMapper() {
		return new DefaultRowMapper<T>();
	}

	/**
	 * Effettua un mapping con le colonne dichiarate.
	 * 
	 * @author ermanno
	 * 
	 * @param <A>
	 */
	private class DefaultRowMapper<A> implements RowMapper<A> {

		@SuppressWarnings("unchecked")
		public A mapRow(ResultSet r, int rn) throws SQLException {
			A t = (A) BeanUtils.instantiateClass(entityClazz);
			// log.debug("istantiated " + t);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(t);
			outer: for (int i = 0; i < pd.length; i++) {
				String name = pd[i].getName();
				// log.debug("prop name: " + name);
				if (CLASS_PROP_NAME.equals(name))
					continue;
				for (String prop : columnMappings.keySet()) {
					if (name.equals(prop)) {
						String mappedColumn = columnMappings.get(prop);
						Object object = r.getObject(mappedColumn);
						bw.setPropertyValue(name, object);
						// log.debug("assigned: " + name + " = " + object
						// + "; mapped column: " + mappedColumn);
						continue outer;
					}
				}

				// TODO pensare a come popolare meglio le entita' chiedendole al
				// lato uno
				// gestire le eventuali annotazioni mancanti

				for (int j = 0; j < manyToOnes.length; j++) {
					if (manyToOnes[j].property().equals(name)) {
						String mappedColumn = manyToOnes[j].column();
						Number l = (Number) r.getObject(mappedColumn);
						GenericDao dao = (GenericDao) context
								.getBean(manyToOnes[j].daoBeanId());
						Entity byId = dao.getById(l);
						bw.setPropertyValue(name, byId);
						// log.debug("assigned: " + name + " = " + byId
						// + "; mapped column: " + mappedColumn);
						continue outer;
					}
				}
				Object object = r.getObject(Util.toUP(name));
				bw.setPropertyValue(name, object);
				// log.debug("assigned: " + name + " = " + object);
			}
			return t;
		}

	}

	@Transactional
	public int update(T t) {
		if (t == null) {
			log.error("oggetto null, impossibile salvare");
			return 0;
		}
		MapSqlParameterSource source = new MapSqlParameterSource();
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(t);
		StringBuilder sb = new StringBuilder(UPDATE);
		sb.append(tablename).append(SET);
		outer: for (int i = 0; i < pd.length; i++) {
			String s = pd[i].getName();
			log.debug("property: " + s);
			if (isGeneratedId && s.equals(ID_PROP_NAME))
				continue;
			if (s.equals(CLASS_PROP_NAME))
				continue;
			for (int j = 0; j < manyToOnes.length; j++) {
				if (manyToOnes[j].property().equals(s)) {
					sb.append(manyToOnes[j].column()).append(EQUALS_COLON)
							.append(s).append(COMMA_W_SPACE);
					Entity<?> e = (Entity<?>) bw.getPropertyValue(manyToOnes[j]
							.property());
					source.addValue(s, e.getId());
					continue outer;
				}
			}
			String colName = columnMappings.get(s);
			sb.append(colName).append(EQUALS_COLON).append(s).append(
					COMMA_W_SPACE);
			source.addValue(s, bw.getPropertyValue(s));
		}
		sb.setLength(sb.length() - 2);
		String idPropName = columnMappings.get(ID_PROP_NAME);
		sb.append(WHERE).append(
				idPropName == null ? Util.toUP(ID_PROP_NAME) : idPropName)
				.append(EQUALS_COLON).append(ID_PROP_NAME).append(SEMICOLON);
		source.addValue(ID_PROP_NAME, bw.getPropertyValue(ID_PROP_NAME));

		sb.append(SEMICOLON);
		log.debug("update sql string: " + sb);
		return template.update(sb.toString(), source);
	}

	@Transactional
	public int[] insertBatch(List<T> list) {
		if (list == null || list.size() == 0) {
			log.error("lista vuota o null, impossibile salvare");
			return new int[0];
		}
		StringBuilder sb = new StringBuilder(INSERT);
		sb.append(tablename).append(SPACE).append(BRACKET_OPEN);
		List<String> propNames = new ArrayList<String>();
		for (String k : insertMapping.keySet()) {
			propNames.add(k);
		}
		for (int i = 0; i < propNames.size(); i++) {
			sb.append(insertMapping.get(propNames.get(i)))
					.append(COMMA_W_SPACE);
		}
		sb.setLength(sb.length() - 2);
		sb.append(BRACKET_CLOSED).append(VALUES).append(BRACKET_OPEN);
		for (int i = 0; i < propNames.size(); i++) {
			sb.append(COLON).append(propNames.get(i)).append(COMMA_W_SPACE);
		}
		sb.setLength(sb.length() - 2);
		sb.append(BRACKET_CLOSED).append(SEMICOLON);
		log.debug("batch insert sql: " + sb);
		return template.batchUpdate(sb.toString(),
				getBatchSqlParameterSource(list));
	}

	@Transactional(readOnly = true)
	public long countAll() {
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(COUNT_ALL).append(FROM).append(tablename).append(SEMICOLON);
		log.debug("count sql: " + sb);
		return template.queryForLong(sb.toString(), EMPTY_ARR);
	}

	@Transactional(readOnly = true)
	public long calcByProperty(StatFunc function, String propNameToFunction,
			ComparationClause clause) {
		check(propNameToFunction);
		String colName = columnMappings.get(propNameToFunction);
		// if (colName == null)
		// colName = Util.toUP(propNameToFunction);
		if (propNameToFunction.equals(STAR))
			colName = STAR;
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(
				Fn.get(function, colName, (colName.equals(STAR) ? "result"
						: function + "_" + colName))).append(FROM).append(
				tablename);
		ClauseBuilder cb = null;
		if (clause != null) {
			sb.append(WHERE);
			cb = new ClauseBuilder(columnMappings, clause);
			sb.append(cb.getClauseAsString());
		}

		sb.append(SEMICOLON);
		log.debug("function sql: " + sb);
		log
				.debug("parameter source:"
						+ (cb == null ? "<null>" : cb.getParameterSource()
								.getValues()));
		if (cb == null)
			return template.queryForLong(sb.toString(), EMPTY_ARR);
		else
			return template
					.queryForLong(sb.toString(), cb.getParameterSource());

	}

	@Transactional(readOnly = true)
	public long countByPropertyValue(String propName, Comparation c,
			Object value) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(COUNT_ALL).append(FROM).append(tablename).append(WHERE);
		sb.append(colName).append(c.val()).append(COLON).append(propName)
				.append(SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		log.debug("count sql: " + sb);
		return template.queryForLong(sb.toString(), source);
	}

	@Transactional(readOnly = true)
	public long countByPropertyValue(String propName, Object value) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(COUNT_ALL).append(FROM).append(tablename).append(WHERE);
		sb.append(colName).append(EQUALS_COLON).append(propName).append(
				SEMICOLON);
		MapSqlParameterSource source = new MapSqlParameterSource(propName,
				value);
		log.debug("count sql: " + sb);
		return template.queryForLong(sb.toString(), source);
	}

	@Transactional(readOnly = true)
	public List<Map<String, Object>> countGroupedByPropertyName(
			String propName, String countColumnName) {
		check(propName);
		String colName = columnMappings.get(propName);
		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(colName).append(COMMA_W_SPACE).append(
				Fn.COUNT("*", countColumnName)).append(FROM).append(tablename)
				.append(GROUP_BY);
		sb.append(colName).append(SEMICOLON);
		log.debug("group count sql: " + sb);
		return template.queryForList(sb.toString(), EMPTY_ARR);
	}

	@Transactional(readOnly = true)
	public List<Map<String, Object>> countGroupedByPropertyName(
			String propName, String countColumnName, Clause... where) {
		check(propName);
		String colName = columnMappings.get(propName);

		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(colName).append(COMMA_W_SPACE).append(
				Fn.COUNT("*", countColumnName)).append(FROM).append(tablename);
		ClauseBuilder cb = null;
		if (where != null && where.length > 0) {
			sb.append(WHERE);
			cb = new ClauseBuilder(columnMappings, where);
			sb.append(cb.getClauseAsString());
		}

		sb.append(GROUP_BY).append(colName);
		sb.append(SEMICOLON);
		log.debug("group count sql: " + sb);
		log
				.debug("parameter source:"
						+ (cb == null ? "<null>" : cb.getParameterSource()
								.getValues()));
		if (cb == null)
			return template.queryForList(sb.toString(), EMPTY_ARR);
		else
			return template
					.queryForList(sb.toString(), cb.getParameterSource());

	}

	@Transactional(readOnly = true)
	public List<Map<String, Object>> countGroupedByPropertyName(
			String propName, String countColumnName, HavingClause having,
			Clause... where) {
		check(propName);
		String colName = columnMappings.get(propName);

		// if (colName == null)
		// colName = Util.toUP(propName);
		StringBuilder sb = new StringBuilder(SELECT);
		sb.append(colName).append(COMMA_W_SPACE).append(
				Fn.COUNT("*", countColumnName)).append(FROM).append(tablename);
		ClauseBuilder cb = null;
		if (where != null && where.length > 0) {
			sb.append(WHERE);
			cb = new ClauseBuilder(columnMappings, where);
			sb.append(cb.getClauseAsString());
		}

		sb.append(GROUP_BY).append(colName);
		if (having != null) {
			sb.append(HAVING).append(countColumnName).append(
					having.getDiscriminator());

		}
		sb.append(SEMICOLON);
		log.debug("group count sql: " + sb);
		return template.queryForList(sb.toString(), cb == null ? EMPTY_ARR : cb
				.getParameterSource());

	}

	@Transactional(readOnly = true)
	public List<T> filter(Clause[] clauses, Order... orderFields) {
		check(clauses);
		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		ClauseBuilder cb = new ClauseBuilder(columnMappings, clauses);
		sb.append(cb.getClauseAsString());
		if (orderFields != null && orderFields.length > 0) {
			sb.append(ORDER_BY);
			for (Order o : orderFields) {
				String colName2 = columnMappings.get(o.getField());
				// if (colName2 == null)
				// colName2 = Util.toUP(o.getField());
				sb.append(colName2).append(o.isAsc() ? ASC : DESC).append(
						COMMA_W_SPACE);
			}
			sb.setLength(sb.length() - 2);
		}
		sb.append(SEMICOLON);
		log.debug("filter sql: " + sb);
		List<T> ret = template.query(sb.toString(), getRowMapper(), cb
				.getParameterSource());
		return ret;
	}

	@Transactional(readOnly = true)
	public List<T> filter(String propName, Object value) {
		return filter(Clause.equal(propName, value));
	}

	@Transactional(readOnly = true)
	public List<T> filter(String propName, Object value, Order... orderFields) {
		return filter(new Clause[] { Clause.equal(propName, value) },
				orderFields);
	}

	@Transactional(readOnly = true)
	public List<T> filter(Clause... clauses) {
		check(clauses);
		StringBuilder sb = new StringBuilder(SELECT_ALL_FROM);
		sb.append(tablename).append(WHERE);
		ClauseBuilder cb = new ClauseBuilder(columnMappings, clauses);
		sb.append(cb.getClauseAsString());
		sb.append(SEMICOLON);
		log.debug("filter sql: " + sb);
		List<T> ret = template.query(sb.toString(), getRowMapper(), cb
				.getParameterSource());
		return ret;
	}

	public void setApplicationContext(ApplicationContext arg0)
			throws BeansException {
		context = arg0;
	}

	private void check(Object obj) {
		if (!Obj.isValid(obj)) {
			throw new IllegalArgumentException("Oggetto non valido: " + obj);
		}
	}
}
